namespace VirtualCity.Initialization.City

open Casanova
open Casanova.Utilities
open Casanova.Core
open Casanova.Coroutines
open Casanova.Game
open Casanova.Input
open Casanova.Math
open Casanova.Drawing
open VirtualCity
open VirtualCity.World
open VirtualCity.City
open VirtualCity.Rendering
open Casanova.StandardLibrary.Core
open Casanova.StandardLibrary.Physics


/// Static class with a set of initializer methods for the 
/// various classes of the city. Some of the initializers
/// may be overloaded.
type Initialize() = 

  static let city_unit_size = 500.0f
  static let draw_scale = 0.5f

  /// Create a new building at a specified position and with a certain texture
  static member NewBuilding(layers,i,j,caption,texture,items) =
    let position         = Vector2.Create(i, j) * city_unit_size
    let caption_position = position - Vector2.Create(0.4f, 0.4f) * city_unit_size
    //let position         = position + Vector2.Create(0.2f, 0.2f) * city_unit_size
    let physical_position = Vector2.Create(i, j) * city_unit_size
    {
       Building.Base     = { Position = physical_position }
       Visual            = DrawableSprite.Create(layers.Buildings, position, 
                                                 Vector2.Create(0.46f), texture)
                                                 //       Visual            = DrawableSprite.Create(layers.Buildings, position, 
//                                                 Vector2.Create(0.4f * draw_scale), texture)
       Caption           = ShadowedText.Create(layers.Overlay, caption_position, caption, Vector2.One * 5.0f,
                                               Vector2.Create(0.25f * draw_scale), Color.White, Color.Black)
//       ItemsCaption      = DrawableText.Create(layers.Overlay, (caption_position + Vector2.Create(0.1f,0.2f) * city_unit_size) * m_to_pixels, caption,
//                                               Vector2.Create(0.75f * draw_scale), Color.CornflowerBlue)
       Items             = 
        var [
              for (item:CityDescription.ItemDescription) in items do
                yield 
                  { 
                    Item.Base       = item
                    Visual          = None
                    Physical        = None
                    Location        = physical_position
                  }
            ]
    }
  

  /// Create a set of buses that travel across the city grid in N-S and E-W directions,
  /// swinging back and forth. One bus is added every "num_roads_to_skip" roads.
  static member NewBuses(layers:Layers,size,num_roads_to_skip) =
    let size = size
    let l,u = -size/2-1,size/2
    let bus_counter = var 1
    [
      for i = l to u do
        if (i-l) % num_roads_to_skip = 0 || i = u then
          let create_stop i j hour minute bus_number =
            let location = Vector2.Create(j, i) * city_unit_size + Vector2.One * (city_unit_size * 0.5f)
            { 
              BusStop.Base = 
                {
                  Name           = sprintf "%d)%d@%d:%d" bus_number j hour minute
                  BusNumber      = bus_number
                  Location       = location
                  Time           = DateTime(2012, 1, 1, hour, minute, 0)
                }
                //layers.Items->layers.Buildings
              Visual         = DrawableSprite.Create(layers.Buildings, location * 1.0f<_>, Vector2.One * 0.4f * draw_scale, @"Buildings\bus_stop_v1")
              BusWaiters     = var []
            }
          let create_stops hour_jag get_i get_j bus_number = 
            [
              for k = 0 to 1 do
                if i % 4 <= 1 then
                  for j = l to u do
                    if (j-l) % num_roads_to_skip = 0 then
                      let hour = (j-l) * 4 / size + 6 + k * 5
                      let hour = hour + hour_jag
                      let i,j = get_i (i,j), get_j (i,j)
                      yield create_stop i j hour 0 bus_number
                  for j = u downto l do
                    if (j-l) % num_roads_to_skip = 0 then
                      let hour = (u-j) * 4 / size + 11 + k * 5
                      let hour = hour + hour_jag
                      let i,j = get_i (i,j), get_j (i,j)
                      yield create_stop i j hour 0 bus_number
                else
                  for j = u downto l do
                    if (j-l) % num_roads_to_skip = 0 then
                      let hour = (u-j) * 4 / size + 6 + k * 5
                      let hour = hour + hour_jag
                      let i,j = get_i (i,j), get_j (i,j)
                      yield create_stop i j hour 0 bus_number
                  for j = l to u do
                    if (j-l) % num_roads_to_skip = 0 then
                      let hour = (j-l) * 4 / size + 11 + k * 5
                      let hour = hour + hour_jag
                      let i,j = get_i (i,j), get_j (i,j)
                      yield create_stop i j hour 0 bus_number

            ]
          let create_bus get_i get_j bus_number = 
            let stops = create_stops ((i - l) % 3) get_i get_j bus_number
            let current_stop = stops |> List.head
            let max_speed = 25.0f<km/h> |> to_meters_per_second
            {
              Bus.Visual      = DrawableSprite.Create(layers.Items, current_stop.Location * 1.0f<_>, Vector2.One * 0.06f * draw_scale, @"Transportation\bus") // Vector2.One * 0.3f * draw_scale
              Physics         = PhysicalEntity.Create(current_stop.Location * 1.0f<_>, max_speed)
              Stops           = stops
              Base = 
                {
                  TicketPrice = 1.0f<eur>
                  Number      = bus_number
                  MaxSpeed    = max_speed
                }
              Passengers  = var []
            }
          yield create_bus fst snd (i * 2 + 1)
          yield create_bus snd fst (i * 2 + 2)
    ]


  /// Create a set of roads to connect a grid of buildings of a "size x size" grid.
  static member NewRoads(layers:Layers,size) =
    let size = size
    let l,u = -size/2-1,size/2
    [
      for i = l to u do
        for j = l to u-1 do
            let road_start = Vector2.Create(i, j) * city_unit_size
            let road_end   = Vector2.Create(i, j+1) * city_unit_size

            let mk_road name road_start road_end =
              let road_start = road_start + Vector2.One * (city_unit_size * 0.5f)
              let road_end   = road_end + Vector2.One * (city_unit_size * 0.5f)

              let road_start = road_start + Vector2.Create(1.0f,0.0f) * (16.0f * draw_scale)
              let road_end = road_end + Vector2.Create(1.0f,0.0f) * (16.0f * draw_scale)

              { 
             //   Road.Visual   = DrawableLine.Create(layers.Roads, road_start * 1.0f<_>, road_end * 1.0f<_>, @"Tiles\ASPHALT", Color.White, city_unit_size * draw_scale)
                Road.Visual   = DrawableLine.Create(layers.Roads, road_start * 1.0f<_>, road_end * 1.0f<_>, @"Tiles\hroad_l_8", Color.White, city_unit_size * draw_scale*4.0f)
                Base =
                  {
                    Name     = name
                    Start    = road_start
                    End      = road_end
                  }
              }

            yield mk_road (sprintf "N-S %d" (i-l+1)) road_start road_end 

            let road_start = Vector2.Create(j, i) * city_unit_size
            let road_end   = Vector2.Create(j+1, i) * city_unit_size

            yield mk_road (sprintf "E-W %d" (i-l+1)) road_start road_end

    ]

//  static member NewRoads(layers:Layers,size) =
//    let size = size
//    let l,u = -size/2-1,size/2
//    [
//      for i = l to u do
//        let road_start = Vector2.Create(i, l) * city_unit_size
//        let road_end   = Vector2.Create(i, u) * city_unit_size
//
//        let mk_road name road_start road_end =
//          let road_start = road_start + Vector2.One * (city_unit_size * 0.5f)
//          let road_end   = road_end + Vector2.One * (city_unit_size * 0.5f)
//
//          let road_start = road_start + Vector2.Create(1.0f,0.0f) * (16.0f * draw_scale)
//          let road_end = road_end + Vector2.Create(1.0f,0.0f) * (16.0f * draw_scale)
//
//          { 
//         //   Road.Visual   = DrawableLine.Create(layers.Roads, road_start * 1.0f<_>, road_end * 1.0f<_>, @"Tiles\ASPHALT", Color.White, city_unit_size * draw_scale)
//            Road.Visual   = DrawableLine.Create(layers.Roads, road_start * 1.0f<_>, road_end * 1.0f<_>, @"Tiles\hroad_l", Color.White, city_unit_size * draw_scale*4.0f)
//            Base =
//              {
//                Name     = name
//                Start    = road_start
//                End      = road_end
//              }
//          }
//
//        yield mk_road (sprintf "N-S %d" (i-l+1)) road_start road_end 
//
//        let road_start = Vector2.Create(l, i) * city_unit_size
//        let road_end   = Vector2.Create(u, i) * city_unit_size
//
//        yield mk_road (sprintf "E-W %d" (i-l+1)) road_start road_end
//
//    ]


  /// Create a set of buildings on a "size x size" grid. The number
  /// of each building type is specified by the "buildings_number"
  /// parameter. Since it is not guaranteed that the number of cells
  /// in the grid will be sufficient, we normalize the number of 
  /// buildings against the number of cells. We also give priority 
  /// to the smallest number of buildings in order to avoid ending
  /// up with, for example, 0 hospitals.  
  static member NewBuildings(layers,size,buildings_number:List<float32 * (string * string * List<CityDescription.ItemDescription>)>) =
    let l,u = -size/2,size/2
    let buildings_distribution =  
      buildings_number 
        |> List.NormalizeBy id (fun x y -> x,y) 
        |> List.sortBy fst
    let grid_cells =  
      [
       for i = 0 to size do
         yield
           [
             for j = 0 to size do 
               yield i,j
           ] |> List.Randomize
      ] |> List.Randomize
    let grid_cells = [ for x in grid_cells do yield! x ] |> List.Randomize
    let rec distribute (grid:List<_>) buildings =
      [
        match buildings with
        | [] -> ()
        | (p,b) :: buildings ->
          let n = p * (float32 (grid.Length))
          let n = (max n 1.0f) |> int |> min grid.Length
          for x in grid |> Seq.take n do  
            yield x,b
          let grid = grid |> Seq.skip n |> Seq.toList
          let buildings = buildings |> List<_>.NormalizeBy id (fun x y -> x,y)
          yield! distribute grid buildings
      ]

    let grid = Array2D.create (size+1) (size+1) None
    let buildings = distribute grid_cells buildings_distribution
    for (i,j),(caption,texture,items) in buildings do
      grid.[i,j] <- Some(caption,texture,items)
    
    let to_grid x = x - l
    let pick_building i j = 
      grid.[i |> to_grid, j |> to_grid]
    
    [
      for i = l to u do
        for j = l to u do
          let physical = 
            {
              PhysicalEntity.Position  = Rule.Create(Vector2.Create(i, j) * city_unit_size)
              Target    = var(Vector2.Create(i, j) * city_unit_size)
              Velocity  = Rule.Create(Vector2.Zero)
              MaxSpeed  = var 0.0f<m/s>
            }
          match pick_building i j with
          | Some(caption,texture,items) ->
            yield Initialize.NewBuilding(layers, i, j, caption, texture, items)
          | None -> ()
    ]


  /// Initialize a new clock to the first of January 2012, at 7AM.
  static member NewClock() =
    {
      Clock.Time    = Rule.Create(DateTime(2012, 1, 1, 7, 1, 1))
    }


  /// Create the graph that represents the walkable points on roads and buildings, and
  /// the allowed transitions between them.
  static member NewWalkGraph(layers,size) =
    let nodes =
      [
        let size = size
        let mk_nodes l u offset is_road =
          [
            for i = l to u do
              let road_start = Vector2.Create(i, l) * city_unit_size + offset
              let road_end   = Vector2.Create(i, u) * city_unit_size + offset
              for j = 0 to (u-l) do
                let a = float32 j / float32(u-l)
                let position = Vector2<m>.Lerp(road_start, road_end, a)
                yield position,is_road
          ]
        let l,u = -size/2-1,size/2
        yield! mk_nodes l u (Vector2.One * (city_unit_size * 0.5f)) true
        let l,u = -size/2,size/2
        yield! mk_nodes l u Vector2.Zero false
      ]
    let links = 
      [
        for x,rx in nodes do
          for y,ry in nodes do
            let distance = Vector2.Distance(x, y)
            let road_to_road = rx && ry
            let building_to_road = not rx && ry || rx && not ry
            let link_type_ok = building_to_road || road_to_road
            if distance > 0.1f<m> && distance <= city_unit_size * 1.0f<m> + 0.1f<m> && link_type_ok then
              let x = 
                {
                  CityDescription.WalkNode.Position    = x
                  CityDescription.WalkNode.IsBuilding  = rx |> not
                }
              let y = 
                {
                  CityDescription.WalkNode.Position    = y
                  CityDescription.WalkNode.IsBuilding  = ry |> not
                }
              yield x,y
      ]
    {
      CityDescription.WalkGraph.Links = links
    }

  /// Create a new city on a grid and with a certain number of 
  /// buildings for each building kind. A building kind is
  /// represented as a caption and the texture of the building.
  /// Roads are set up across the grid, and bus lines simply
  /// go North-South or East-West at regular intervals.
  static member NewCity(layers,size,buildings_number) =
    {
      Agents        = var []
      BlocksPerSide = size
      BlockSize     = city_unit_size * 1.0f<m>
      Buildings     = Initialize.NewBuildings(layers,size,buildings_number)
      Buses     = Initialize.NewBuses(layers,size,2)
      Roads         = Initialize.NewRoads(layers,size)
      Clock         = Initialize.NewClock()
      WalkGraph     = Initialize.NewWalkGraph(layers,size)
    }
